#include "DirectXClass.h"
#include <stdlib.h>
#include "../../../References/DirectXTK/WICTextureLoader.h"
#include "../../Utils/StringConvert.h"
DirectXClass::DirectXClass()
	//m_nextVertexBufferOffset(0),
	//m_nextIndexBufferOffset(0)
{
}
DirectXClass::~DirectXClass()
{
}
bool DirectXClass::Initialize(int _screenHeight, int _screenWidth, bool _verticalSynchronizationEnabled, bool using4XMSAA,bool _fullScreen,HWND hwnd,float _screenDepth,float _screenNear)
{
	HRESULT result;
	IDXGIFactory* iDXGIFactory;
	IDXGIAdapter* iDXGIAdapter;
	IDXGIOutput* iDXGIOutput;
	DXGI_MODE_DESC* displayModeList;
	DXGI_ADAPTER_DESC adapterDesc;
	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	D3D_FEATURE_LEVEL featureLevel;
	ID3D11Texture2D* backBufferPtr;
	D3D11_TEXTURE2D_DESC depthBufferDesc;
	D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
	D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
	unsigned int numModes;
	unsigned int numerator, denominator;
	unsigned int stringLength;

	verticalSynchronizationEnabled = _verticalSynchronizationEnabled;

	/*
	Initialize Direct3D:
	1. Create the ID3D11Device and ID3D11DeviceContext interfaces.
	2. Check 4X MSAA quality level support.
	3. Describe the characteristics of the swap chain by filling out an instance of the DXGI_SWAP_CHAIN_DESC structure.
	4. Query the IDXGIFactory instance that was used to create the device, and create an IDXGISwapChain instance.
	5. Create a render target view to the swap chain's back buffer.
	6. Create the depth/stencil buffer and its associated depth/stencil view.
	7. Bind the rander target view and depth/stencil view to the output merger stage of the rendering pipeline so that they can be used by Direct3D.
	8. Set the viewpoint.
	(The order of some steps can be changed.)
	*/

	//In practice, we create the IDXGIFactory first.
	result = CreateDXGIFactory(__uuidof(iDXGIFactory), (void**)&iDXGIFactory);
	if (FAILED(result))
	{
		return false;
	}

	//The display adapter we want the create device to represent.
	result = iDXGIFactory->EnumAdapters(0,&iDXGIAdapter);
	if (FAILED(result))
	{
		return false;
	}

	result = iDXGIAdapter->EnumOutputs(0, &iDXGIOutput);
	if (FAILED(result))
	{
		return false;
	}

	result = iDXGIOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
	if (FAILED(result))
	{
		return false;
	}

	displayModeList = new DXGI_MODE_DESC[numModes];
	if (!displayModeList)
	{
		return false;
	}

	for (int i = 0;i < numModes;++i)
	{
		if (displayModeList[i].Width == (unsigned int)_screenWidth)
		{
			if (displayModeList[i].Height == (unsigned int)_screenHeight)
			{
				numerator = displayModeList[i].RefreshRate.Numerator;
				denominator = displayModeList[i].RefreshRate.Denominator;
				break;
			}
		}
	}

	result = iDXGIAdapter->GetDesc(&adapterDesc);
	if (FAILED(result))
	{
		return false;
	}

	videoCardMemory = (int)adapterDesc.DedicatedVideoMemory;

	char _videoCardDescription[128];
	if (wcstombs_s(&stringLength, _videoCardDescription, 128, adapterDesc.Description, 128))
	{
		return false;
	}
	else
	{
		std::string temp(_videoCardDescription);
		videoCardDescription = "" + temp;
	}

	delete[]displayModeList;
	displayModeList = 0;
	iDXGIOutput->Release();
	iDXGIOutput = 0;
	iDXGIAdapter->Release();
	iDXGIAdapter = 0;
	iDXGIFactory->Release();
	iDXGIFactory = 0;

	//Describe the swap chain.
	ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
	swapChainDesc.BufferCount = 1;//The number of back buffers to use in the swap chain. By specifying 1, we mean using double buffering.
	swapChainDesc.BufferDesc.Width = _screenWidth;
	swapChainDesc.BufferDesc.Height = _screenHeight;
	swapChainDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	if (verticalSynchronizationEnabled)
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
	}
	else
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	}
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapChainDesc.OutputWindow = hwnd;
	unsigned int max4xMsaaQuality = D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT;
	if (using4XMSAA)
	{
		swapChainDesc.SampleDesc.Count = 4;
		swapChainDesc.SampleDesc.Quality = max4xMsaaQuality - 1;
	}
	else
	{
		swapChainDesc.SampleDesc.Count = 1;
		swapChainDesc.SampleDesc.Quality = 0;
	}
	if (_fullScreen)
	{
		swapChainDesc.Windowed = false;
	}
	else
	{
		swapChainDesc.Windowed = true;
	}
	swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;//Let the display driver select the most efficient presentation method.
	swapChainDesc.Flags = 0;
	featureLevel = D3D_FEATURE_LEVEL_11_0;
	result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, D3D11_SDK_VERSION, &swapChainDesc, &swapChain, &device, NULL, &deviceContext);
	if (FAILED(result))
	{
		return false;
	}

	//Create a render target view to the swap chain's back buffer.
	result = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBufferPtr));
	if (FAILED(result))
	{
		return false;
	}
	result = device->CreateRenderTargetView(backBufferPtr, NULL, &renderTargetView);
	if (FAILED(result))
	{
		return false;
	}
	backBufferPtr->Release();
	backBufferPtr = 0;

	//Create the depth/stencil buffer and view
	ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));
	depthBufferDesc.Width = _screenWidth;
	depthBufferDesc.Height = _screenHeight;
	depthBufferDesc.MipLevels = 1;//The number of mipmap levels.For creating the depth/stencil buffer, our texture only needs one mipmap level.
	depthBufferDesc.ArraySize = 1;//The number of textures in a texture array. For the depth/stencil buffer, we only need one texture.
	depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	if (using4XMSAA)
	{
		depthBufferDesc.SampleDesc.Count = 4;
		depthBufferDesc.SampleDesc.Quality = max4xMsaaQuality - 1;
	}
	else
	{
		depthBufferDesc.SampleDesc.Count = 1;
		depthBufferDesc.SampleDesc.Quality = 0;
	}
	depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthBufferDesc.CPUAccessFlags = 0;
	depthBufferDesc.MiscFlags = 0;
	result = device->CreateTexture2D(&depthBufferDesc, NULL, &depthStencilBuffer);
	if (FAILED(result))
	{
		return false;
	}

	ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
	depthStencilDesc.DepthEnable = true;
	depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
	depthStencilDesc.StencilEnable = true;
	depthStencilDesc.StencilReadMask = 0xFF;
	depthStencilDesc.StencilWriteMask = 0xFF;
	depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
	depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
	result = device->CreateDepthStencilState(&depthStencilDesc, &depthStencilState);
	if (FAILED(result))
	{
		return false;
	}
	deviceContext->OMSetDepthStencilState(depthStencilState, 1);

	ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));
	depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	depthStencilViewDesc.Texture2D.MipSlice = 0;
	result = device->CreateDepthStencilView(depthStencilBuffer, &depthStencilViewDesc, &depthStencilView);
	if (FAILED(result))
	{
		return false;
	}
	deviceContext->OMSetRenderTargets(1, &renderTargetView, depthStencilView);

	//Create two raster states: solid and wireframe, and set the device to solid mode by default.
	D3D11_RASTERIZER_DESC solidRasterDesc;
	solidRasterDesc.FillMode = D3D11_FILL_SOLID;
	solidRasterDesc.CullMode = D3D11_CULL_BACK;
	solidRasterDesc.FrontCounterClockwise = false;
	solidRasterDesc.DepthBias = 0;
	solidRasterDesc.DepthBiasClamp = 0.0f;
	solidRasterDesc.SlopeScaledDepthBias = 0.0f;
	solidRasterDesc.DepthClipEnable = true;
	solidRasterDesc.ScissorEnable = false;
	solidRasterDesc.MultisampleEnable = false;
	solidRasterDesc.AntialiasedLineEnable = false;
	result = device->CreateRasterizerState(&solidRasterDesc, &m_solidRasterState);
	if (FAILED(result))
	{
		return false;
	}
	D3D11_RASTERIZER_DESC wireframeRasterDesc;
	wireframeRasterDesc.FillMode = D3D11_FILL_WIREFRAME;
	wireframeRasterDesc.CullMode = D3D11_CULL_BACK;
	wireframeRasterDesc.FrontCounterClockwise = false;
	wireframeRasterDesc.DepthBias = 0;
	wireframeRasterDesc.DepthBiasClamp = 0.0f;
	wireframeRasterDesc.SlopeScaledDepthBias = 0.0f;
	wireframeRasterDesc.DepthClipEnable = true;
	wireframeRasterDesc.ScissorEnable = false;
	wireframeRasterDesc.MultisampleEnable = false;
	wireframeRasterDesc.AntialiasedLineEnable = false;
	result = device->CreateRasterizerState(&wireframeRasterDesc, &m_wireframeRasterState);
	if (FAILED(result))
	{
		return false;
	}
	deviceContext->RSSetState(m_solidRasterState);

	//Set the viewpoint
	D3D11_VIEWPORT viewport;
	viewport.Width = (float)_screenWidth;
	viewport.Height = (float)_screenHeight;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	viewport.TopLeftX = 0.0f;
	viewport.TopLeftY = 0.0f;
	deviceContext->RSSetViewports(1, &viewport);

	float fieldOfView, screenAspect;
	fieldOfView = (float)DirectX::XM_PI / 4.0f;
	screenAspect = (float)_screenWidth / (float)_screenHeight;

	projectionMatrix = DirectX::XMMatrixPerspectiveFovLH(fieldOfView, screenAspect, _screenNear, _screenDepth);
	orthoMatrix = DirectX::XMMatrixOrthographicLH(_screenWidth, _screenHeight, _screenNear, _screenDepth);

	return true;
}
void DirectXClass::Shutdown()
{
	if (swapChain)
	{
		swapChain->SetFullscreenState(false, NULL);
	}
	if (m_solidRasterState)
	{
		m_solidRasterState->Release();
		m_solidRasterState = 0;
	}
	if (m_wireframeRasterState)
	{
		m_wireframeRasterState->Release();
		m_wireframeRasterState = 0;
	}
	if (depthStencilView)
	{
		depthStencilView->Release();
		depthStencilView = 0;
	}
	if (depthStencilState)
	{
		depthStencilState->Release();
		depthStencilState = 0;
	}
	if (depthStencilBuffer)
	{
		depthStencilBuffer->Release();
		depthStencilBuffer = 0;
	}
	if (renderTargetView)
	{
		renderTargetView->Release();
		renderTargetView = 0;
	}
	if (deviceContext)
	{
		deviceContext->Release();
		deviceContext = 0;
	}
	if (device)
	{
		device->Release();
		swapChain = 0;
	}
}
void DirectXClass::BeginScene(float red, float green, float blue, float alpha)
{
	float color[4];
	color[0] = red;
	color[1] = green;
	color[2] = blue;
	color[3] = alpha;
	deviceContext->ClearRenderTargetView(renderTargetView, color);
	deviceContext->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
}
void DirectXClass::EndScene()
{
	if (verticalSynchronizationEnabled)
	{
		swapChain->Present(1, 0);
	}
	else
	{
		swapChain->Present(0, 0);
	}
}
ID3D11Device* DirectXClass::GetDevice()
{
	return device;
}
ID3D11DeviceContext* DirectXClass::GetDeviceContext()
{
	return deviceContext;
}
DirectX::XMMATRIX DirectXClass::GetProjectionMatrix()
{
	return projectionMatrix;
}
DirectX::XMMATRIX DirectXClass::GetOrthoMatrix()
{
	return orthoMatrix;
}
std::string DirectXClass::GetVideoCardDesCription()
{
	return videoCardDescription;
}
int DirectXClass::GetVideoCardMemory()
{
	return videoCardMemory;
}
void DirectXClass::SetRasterizerState(bool wireframeMode)
{
	if (wireframeMode)
	{
		deviceContext->RSSetState(m_wireframeRasterState);
	}
	else
	{
		deviceContext->RSSetState(m_solidRasterState);
	}
}
void DirectXClass::CreateRenderingBuffer(GameObject* gameObject)
{
	std::vector<Vertex>vertexes = gameObject->GetVertexes();
	std::vector<std::vector<int>>indexes = gameObject->GetIndexes();

	//Create vertex buffer
	DirectXVertexType* directXVertexes = new DirectXVertexType[vertexes.size()];
	for (int i = 0;i < vertexes.size();++i)
	{
		directXVertexes[i].directXPositionLocal = vertexes[i].GetPosition();
		directXVertexes[i].directXNormalLocal = vertexes[i].GetVertexNormal();
		directXVertexes[i].directXUVCoordinate = vertexes[i].GetUVCoordinate();
	}
	D3D11_BUFFER_DESC vertexBufferDesc;
	vertexBufferDesc.ByteWidth = sizeof(DirectXVertexType)*vertexes.size();
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = 0;
	vertexBufferDesc.MiscFlags = 0;
	vertexBufferDesc.StructureByteStride = 0;
	D3D11_SUBRESOURCE_DATA vertexData;
	vertexData.pSysMem = directXVertexes;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;
	device->CreateBuffer(&vertexBufferDesc, &vertexData, gameObject->GetVertexBuffer());
	delete[] directXVertexes;

	//Create index buffer
	int* directXIndexes = new int[indexes.size() * 3];
	for (int i = 0;i < indexes.size();++i)
	{
		for (int j = 0;j < 3;++j)
		{
			directXIndexes[3 * i + j] = indexes[i][j];
		}
	}
	D3D11_BUFFER_DESC indexBufferDesc;
	indexBufferDesc.ByteWidth = sizeof(int)*(indexes.size() * 3);
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;
	indexBufferDesc.StructureByteStride = 0;
	D3D11_SUBRESOURCE_DATA indexData;
	indexData.pSysMem = directXIndexes;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;
	device->CreateBuffer(&indexBufferDesc, &indexData, gameObject->GetIndexBuffer());
	delete[] directXIndexes;
}
void DirectXClass::CreateTexture(GameObject* gameObject)
{
	std::string ambientMapFileName = gameObject->GetAmbientTextureFileName();
	std::string diffuseMapFileName = gameObject->GetDiffuseTextureFileName();
	std::string specularMapFileName = gameObject->GetSpecularTextureFileName();
	HRESULT result;
	if (ambientMapFileName != "")
	{
		wchar_t* fileName = StringConvert::ToWchar_t(ambientMapFileName);
		result=DirectX::CreateWICTextureFromFile(device, deviceContext, fileName, (ID3D11Resource**)&(gameObject->m_ambientMapTexture), &(gameObject->m_ambientMapShaderResourceView));
	}
	if (diffuseMapFileName != "")
	{
		wchar_t* fileName = StringConvert::ToWchar_t(diffuseMapFileName);
		result=DirectX::CreateWICTextureFromFile(device, deviceContext, fileName, (ID3D11Resource**)&(gameObject->m_diffuseMapTexture), &(gameObject->m_diffuseMapShaderResourceView));
	}
	if (specularMapFileName != "")
	{
		wchar_t* fileName = StringConvert::ToWchar_t(specularMapFileName);
		result=DirectX::CreateWICTextureFromFile(device, deviceContext, fileName, (ID3D11Resource**)&(gameObject->m_specularMapTexture), &(gameObject->m_specularMapShaderResourceView));
	}
}